//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "vga.h"

#ifdef _DEBUG
#define Assert(x)   if (!((bool)(x))) { \
                        __asm__ __volatile__("int $3"); \
                    }
#else
#define Assert(x)
#endif

INLINE void SetCursorShape(bool_t bShape)
{
    if (bShape) {
        Outb(VGA_ADDRESS_REGISTER, CURSOR_SCANLINE_START);
        Outb(VGA_DATA_REGISTER, 0x02);
        Outb(VGA_ADDRESS_REGISTER, CURSOR_SCANLINE_END);
        Outb(VGA_DATA_REGISTER, 0x0b);
    }
    else {
        Outb(VGA_ADDRESS_REGISTER, CURSOR_SCANLINE_START);
        Outb(VGA_DATA_REGISTER, 0x0d);
        Outb(VGA_ADDRESS_REGISTER, CURSOR_SCANLINE_END);
        Outb(VGA_DATA_REGISTER, 0x0e);
    }
}

INLINE void SetCursorLocation(uint_t uCursorLocation)
{
    Outb(VGA_ADDRESS_REGISTER, CURSOR_LOCATION_LOW);
    Outb(VGA_DATA_REGISTER,    LOBYTE(uCursorLocation));
    Outb(VGA_ADDRESS_REGISTER, CURSOR_LOCATION_HIGH);
    Outb(VGA_DATA_REGISTER,    HIBYTE(uCursorLocation));
}

//
// The vga driver's classfactory
//
EXTERN IDeviceDriver * CDECL CreateVGA(uint_t uDeviceNo, void *pvParameter)
{
    Video *pVGADriver = new Video;
    if (NULL == pVGADriver) {
        Assert(0 && "There is not enough memeory!");
        return NULL;
    }

    pVGADriver->AddRef();

    return pVGADriver;
}

ECode Video::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode Video::Write(
    /* [in] */ Int64 u64Offset, // start address
    /* [in] */ const MemoryBuf & buffer, // print context
    /* [out] */ Int32 * pBytesWritten, // printed
    /* [out] */ IEvent * * ppCompletionEvent)
{
    wchar_t *pwcString = (wchar_t *)buffer.GetPayload();
    uint32_t uLength = buffer.GetUsed() / 2;

    if (0 == uLength || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    // If appointment start address is bigger than the screen buffer, the
    // Cursor immovability.
    if (u64Offset < m_uScreenColumns * m_uScreenRows) {
        m_uCursorLocation = u64Offset;
    }
    else {
        u64Offset = m_uCursorLocation;
    }

    // Output the string
    for (uint32_t i = 0;
        (i < uLength)
        && (m_uCursorLocation < (m_uScreenColumns * m_uScreenRows));
        i++) {
        m_pTextChars[m_uCursorLocation++] =
            TEXT_CHAR(pwcString[i], m_bDefaultTextColor);
    }

    SetCursorLocation(m_uCursorLocation);

    if (pBytesWritten) {
        *pBytesWritten = m_uCursorLocation - u64Offset;
        Assert(*pBytesWritten >= 0);
    }

    return NOERROR;
}

ECode Video::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if ((nControlCode < 0) || (nControlCode > 4)
        || (ppCompletionEvent && *ppCompletionEvent)) {
        return E_INVALID_ARGUMENT;
    }

    // Set the cursor's position
    if (0 == nControlCode) {
        if (inBuffer.GetUsed() < (Int32)sizeof(UInt32)) {
            return E_INVALID_ARGUMENT;
        }

        uint32_t uPosition = *(uint32_t *)inBuffer.GetPayload();

        if (uPosition >= m_uScreenColumns * m_uScreenRows) {
            return E_INVALID_ARGUMENT;
        }

        SetCursorLocation(uPosition);
        m_uCursorLocation = uPosition;

        return NOERROR;
    }

    // Get the cursor's position
    if (1 == nControlCode) {
        if (pOutBuffer == NULL ||
            ((uint32_t)pOutBuffer->GetCapacity () < sizeof(m_uCursorLocation))) {
            return E_INVALID_ARGUMENT;
        }

        *(uint32_t *)pOutBuffer->GetPayload() = m_uCursorLocation;

        pOutBuffer->SetUsed(sizeof(m_uCursorLocation));

        return NOERROR;
    }

    // Get the screen column
    if (2 == nControlCode) {
        if (pOutBuffer == NULL ||
            ((uint32_t)pOutBuffer->GetCapacity() < sizeof(m_uCursorLocation))) {
            return E_INVALID_ARGUMENT;
        }

        *(uint32_t *)pOutBuffer->GetPayload() = m_uScreenColumns;

        pOutBuffer->SetUsed(sizeof(m_uScreenColumns));

        return NOERROR;
    }

    // Get the screen row
    if (3 == nControlCode) {
        if (pOutBuffer == NULL ||
            ((uint32_t)pOutBuffer->GetCapacity () < sizeof(m_uCursorLocation))) {
            return E_INVALID_ARGUMENT;
        }

        *(uint32_t *)pOutBuffer->GetPayload() = m_uScreenRows;
        pOutBuffer->SetUsed(sizeof(m_uScreenRows));

        return NOERROR;
    }

    // Set the cursor shape
    if (4 == nControlCode) {
        if (inBuffer.IsNullOrEmpty()) {
            return E_INVALID_ARGUMENT;
        }

        SetCursorShape(*(bool_t *)inBuffer.GetPayload());
        return NOERROR;
    }

    return E_NOT_IMPLEMENTED;
}
